/***************************************************************

Recursive Quicksort
The quicksort works by partitioning a list into two smaller lists and
recursively calling the quicksort to partition each sub list until each
sublist contains a single item.

Method

Begin by choosing a value from the list (linear array) and label it as
the pivot value.
Partition the list into two parts separated by the item containing
the pivot value.
The list is rearranged by exchanging items so that
items in the lefthand partition are <= to the pivot value, and
items in the righthand partition are > the pivot value

i.e.. If first and last are the subscripts (indexes) of the first and last
items in the list then:

Rearrange the list so that all items with values <= the pivot
value have subscripts less than the subscript of the pivot value. Items
with values > the pivot value will have subscripts > the
subscript of the pivot value.
(Recursive part)
     Repeat steps 1 & 2 using the left hand partition
     Repeat steps 1 & 2 using the right hand partition

Continue until the list is sorted.

Choosing the pivot value
The initial pivot value can be any value from the list.
Normally the first element is selected as the pivot value.
When working with lists that are partially sorted, a good choice for
the pivot value is the middle element from the list.


Additional optimizations were made:

1) On webapp server all date strings are converted into dates and
   then into numbers. All elements in filtered columns are assigned the
   numeric value corresponding to index of its value in the filter menu
   (which is sorted); then these numbers are converted into strings and
   the compare function is set to numeric.  These values are assign to
   a hidden field in the cell definition

2) all text in column is converted to the appropriate type
   (e.g.  upper case for case insensitive, number if column is a column
   of numbers etc) before sort is begun.

3) Sorted column is saved - so that each column is in fact only ever sorted once.
   Reverse method on array is used  to reverse sort
************************************************************************/
function  quickSort2(rows, col, cmpFunc, partition, first, last)
{
   var i = 0;
   var j = 0;
   var left = first;
   var right = last;
   var stack_pointer = -1;
   var stack = new Array();
   var swap , temp;
   while(true)
   {
      if(right - left <= 7)
      {
         for(j=left+1;j<=right;j++)
         {
            swap = rows[j];
            i = j-1;
            while(i>=left && (cmpFunc(rows[i].cells[col].sortValue , swap.cells[col].sortValue) > 0))
               rows[i+1] = rows[i--];
               rows[i+1] = swap;
         }
         if(stack_pointer == -1)
            break;
         right = stack[stack_pointer--];
         left = stack[stack_pointer--];
      }
      else
      {
         var median = Math.round((left + right) / 2);
         i = left + 1;
         j = right;
         swap = rows[median]; rows[median] = rows[i]; rows[i] = swap;
         /* make sure: c[left] <= c[left+1] <= c[right] */
         if(cmpFunc(rows[left].cells[col].sortValue , rows[right].cells[col].sortValue) > 0)
         {
            swap = rows[left]; rows[left] = rows[right]; rows[right] = swap;
         }
         if(cmpFunc(rows[i].cells[col].sortValue , rows[right].cells[col].sortValue) > 0)
         {
            swap = rows[i]; rows[i] = rows[right]; rows[right] = swap;
         }
         if(cmpFunc(rows[left].cells[col].sortValue , rows[i].cells[col].sortValue) > 0)
         {
            swap = rows[left]; rows[left] = rows[i]; rows[i] = swap;
         }
         temp = rows[i];
         while(true)
         {
            do i++; while(cmpFunc(rows[i].cells[col].sortValue , temp.cells[col].sortValue) < 0);
            do j--; while(cmpFunc(rows[j].cells[col].sortValue , temp.cells[col].sortValue) > 0);
            if(j < i)
               break;
            swap = rows[i]; rows[i] = rows[j]; rows[j] = swap;
         }
         rows[left + 1] = rows[j];
         rows[j] = temp;
         if(right-i+1 >= j-left)
         {
            stack[++stack_pointer] = i;
            stack[++stack_pointer] = right;
            right = j-1;
         }
         else
         {
            stack[++stack_pointer] = left;
            stack[++stack_pointer] = j-1;
            left = i;
         }
      }
   }
}

function  quickSort(rows, col, cmpFunc, partition, first, last)
{

   if( first < last)
   {
      var pivotIndex = partition( rows, col, cmpFunc, first, last );
      quickSort(rows, col, cmpFunc, partition, first, pivotIndex - 1);
      quickSort(rows, col, cmpFunc, partition, pivotIndex + 1, last);
   }
}

function partition(rows, col, cmpFunc, first, last)
{
   var  pivotIndex;
   var  pivotValue;
   var  up = first;
   var  down = last;

   pivotValue = rows[first].cells[col].sortValue;

   do{
      // move up to the next location > pivot
      while((cmpFunc(rows[up].cells[col].sortValue, pivotValue) <= 0)
             && (up < last))
        up++;

      // move down to the next location <= pivot
      while (cmpFunc(rows[down].cells[col].sortValue, pivotValue)  > 0)
        down--;

      if (up < down)
        swap(rows, up, down);

   } while (up < down);

   //put pivot value where it belongs and define pivotIndex

   swap(rows, first, down);
   pivotIndex = down;
   return pivotIndex;
}


function swap(rows, i, j)  // swaps array elements i,j
{
   var temp;

   temp = rows[i];
   rows[i] = rows[j];
   rows[j] = temp;
}


//-----------------------------------------------------------------------------
// Function to get text value for a sort (FIRST LEVEL ONLY) recursion
// uses getTextValue The purpose of this function is to get hidden value if
// any for sorting purposes. If there are no sort value at first level then
// use standard getTextValue
//-----------------------------------------------------------------------------
function getSortTextValue(el)
{

   var i;
   var s;

   // Find and concatenate the values of all text nodes contained within the
   // element.
   s = "";
   for (i = 0; i < el.childNodes.length; i++)
   {
      if (el.childNodes[i].nodeType == document.TEXT_NODE)
      {
         if (el.childNodes[i].nodeValue == datatableBlankCompareValue)
           s += " ";
         else
           s += el.childNodes[i].nodeValue
      }
      // at first level if we find a hidden input field the return it
      // for the sort value
      else if (el.childNodes[i].nodeType == document.ELEMENT_NODE
               && el.childNodes[i].tagName == "INPUT"
               && el.childNodes[i].type == datatableHiddenTypeCompareValue)
         return el.childNodes[i].value;
      else if (el.childNodes[i].tagName == "BR")
         s += " ";
       else
         // Use normal getTextValue for recursion,
         // to get text within sub-elements.
         s += getTextValue(el.childNodes[i]);
   }
   return s;
}

// the following function set number values for a column
// in preparation for sort
function setNumbers(rows, col)
{
   var i;
   for (i = 0; i < rows.length; i++)
   {
      var row = rows[i];
      var cellText = getSortTextValue(row.cells[col]);
      row.cells[col].sortValue = parseFloat(cellText);
   }
}

// the following function set date values for a column
// in preparation for sort
function setDates(rows, col)
{
   var i;
   for (i = 0; i < rows.length; i++)
   {
      var row = rows[i];
      var cellText = getSortTextValue(row.cells[col]);
      row.cells[col].sortValue = Date.parse(cellText.replace(/\-/g, '/'));
   }
}

// the following function set right justified values for a column
// in preparation for sort
function rightJustifyValues(rows, col, maxlen)
{
   var i, pad = 0;
   var blank = "                                              ";
   var tmpString;
   while (blank.length < maxlen)
      blank = blank.concat("                                 ");
   for (i = 0; i < rows.length; i++)
   {
      var row = rows[i];
      if (maxlen > row.cells[col].sortValue.length)

      {
         pad = maxlen - row.cells[col].sortValue.length;
         tmpString = blank.substr(0, pad) + row.cells[col].sortValue;
         row.cells[col].sortValue = tmpString;
      }
   }
}
// the following function set right justified values for a column
// in preparation for sort
function setDisplayedName(rows, col)
{
   var i;
   var blank = "                                                                  ";
   var zeros = "000000000000000000000000000000000000000000000000000000000000000000";
   var tmpString;
   var names = new Array();
   var revs  = new Array();
   var seqs  = new Array();
   var vers  = new Array();
   var maxRevPad = 0;
   var maxSeqPad = 0;
   var maxVerPad = 0;
   var revPad = 0;
   var seqPad = 0;
   var verPad = 0;
   // xxxxx,A,1,1: 'xxxxx' = name, 'A' = revision, '1' = sequence, and last '1' = version
   var row;
   for (i = 0; i < rows.length; i++)
   {
      row = rows[i];
      tmpString = getSortTextValue(row.cells[col]);
      var splitName = tmpString.split(datatableDisplayedNameDelimiter);


      names[i] = splitName[0];
      if (splitName.length > 1)
         revs[i] = splitName[1];
      else
         revs[i] = "";
      if (splitName.length > 2)
         seqs[i] = splitName[2];
      else
         seqs[i] = "";
      if (splitName.length > 3)
         vers[i] = splitName[3];
      else
         vers[i] = "";
      if (maxRevPad < revs[i].length)
         maxRevPad = revs[i].length;
      if (maxSeqPad < seqs[i].length)
         maxSeqPad = seqs[i].length;
      if (maxVerPad < vers[i].length)
         maxVerPad = vers[i].length;

   }

   for (i = 0; i < rows.length; i++)
   {
      row = rows[i];
      revPad = maxRevPad - revs[i].length;
      seqPad = maxSeqPad - seqs[i].length;
      verPad = maxVerPad - vers[i].length;
      tmpString = names[i] +
                  blank.substr(0, revPad) + revs[i] +
                  zeros.substr(0, seqPad) + seqs[i] +
                  zeros.substr(0, verPad) + vers[i];
      row.cells[col].sortValue = tmpString.toUpperCase();
   }
}


// the following function set right justified values (both alpha and numeric)
// for a column in preparation for sort
function rightJustifyAlphhaNumericValues(rows, col, maxlen)
{
   var i, pad = 0;
   var zeros = "000000000000000000000";
   maxlen++; // make sure all strings have at least one pad character
   while (zeros.length < maxlen)
      zeros = zeros.concat("000000000000000000000");

   var tmpString;
   for (i = 0; i < rows.length; i++)
   {
      var row = rows[i];
      var sortValue = row.cells[col].sortValue;
      var length = row.cells[col].sortValue.length;
      if (maxlen > length)
      {
         if (isInt(sortValue) == false)
            tmpString = "9" + sortValue;
         else
         {
            pad = maxlen - length;
            tmpString = zeros.substr(0, pad) + sortValue;
         }
      }
      row.cells[col].sortValue = tmpString;
   }
}
// the following function determine whether or not a string is
// all integers
function isInt(str)
{
   var tmpChar;
   for (var i = 0; i < str.length; i++)
   {
      tmpChar = str.charAt(i);
      if (tmpChar.localeCompare("0") < 0 ||
          tmpChar.localeCompare("9") > 0)
         return false;
   }
   return true;
}


// the following function set case insensitive values for a column
// in preparation for sort
function setCaseInsensitiveStrings(rows, col)
{
   var i, len = 0;
   for (i = 0; i < rows.length; i++)
   {
      var row = rows[i];
      var cellText = getSortTextValue(row.cells[col]);
      row.cells[col].sortValue = String(cellText).toUpperCase();
      if (len < row.cells[col].sortValue.length)
         len = row.cells[col].sortValue.length;
   }
   return len;
}


// the following function set values for a column
// in preparation for sort
function setValues(rows, col)
{
   var i, len = 0;
   for (i = 0; i < rows.length; i++)
   {
      var row = rows[i];
      row.cells[col].sortValue = getSortTextValue(row.cells[col]);
      if (len < row.cells[col].sortValue.length)
         len = row.cells[col].sortValue.length;
   }
   return len;
}

function giveFeedback(tableId, col, e)
{
   // this routine was being called twice for one event - so
   // after the first call stop propagation of event
   if (!e) e = window.event;
   e.cancelBubble = true;
   if (e.stopPropagation) e.stopPropagation();
   // Get the column header
   var thID = tableId + col;
   var thObj = document.getElementById(thID);
   // set display class to look depressed
   thObj.className = datatableTableHeaderSortableSel;
   thObj.svClass =  datatableTableHeader;
}


//-----------------------------------------------------------------------------
// main Sort function
//-----------------------------------------------------------------------------
// Parameters:
//      - Table ID
//      - col to sort
//      - event which triggered this call
//
// Note that all the tables which are to be sorted must contain an ID tag.
// So, if they do not exist, you must create one for each table. Also, the
// table names/ids MUST BE UNIQUE.
function sortTable(tableId, col, e)
{
   // this routine was being called twice for one event - so
   // after the first call stop propagation of event
   if (!e) e = window.event;
   e.cancelBubble = true;
   if (e.stopPropagation) e.stopPropagation();

   // Get the table (tBody) section to sort.
   var tBody = document.getElementById(tableId);
   var thID = tableId + col;
   var thObj = document.getElementById(thID);
   var oldDsply = tBody.style.display;

   // do the real sort - we need to separate the stopping of
   // event propagation from the actual sort - doSort
   // and doCumulativeSort will be called from initTable also

   // if altKey held down and one column has already been sorted
   // do cumulative sort - using altKey because using shiftKey invokes
   // selection behavior which is cosmetically obnoxious
   if (e.altKey == true &&
       tBody.sortedCols != null &&
       tBody.lastColumn >= 0)
      doCumulativeSort(tBody, thObj, col)
   else
      doSort(tBody, thObj, col)
   // Set the table display style to "none" - necessary for Netscape 6
   // browsers.
   tBody.style.display = "none";
   // restore selections in select column
   resetSelectedAll(tBody);

   // restore odd even colors
   setRowClass(tBody);

   // Restore the tables display style.
   tBody.style.display = oldDsply;
   // Restore the column display class.
   thObj.className = datatableTableHeaderSortable;
}
// This is the function that actually does the sort.
// Notice the event is not passed in.  This function is called both
// from sortTable and initTable which is called when the on load
// event is triggered and we do want to propagate that event
function doSort(tBody, thObj, col)
{
   var tableId = tBody.id;
   var imgID = tableId + datatableImageBase + col;
   var imgObj = document.getElementById(imgID);
   var cmpFuncID = tableId + datatableCompareBase + col;
   var cmpFuncObj = document.getElementById(cmpFuncID);
// get field in which sorted columns is stored and initialize sort direction
   var sortedColumns = document.getElementById(tableId + datatableSortedColumns);
   
   
   var sortDirection = 1;


   // Reset all the sort images for columns stored in sorted cols array
   if(tBody.sortedCols != null)
   {
      for(var i=0; i < tBody.sortedCols.length; i++)
      {
         if(tBody.sortedCols[i].colId == col)
            continue;

         tBody.sortedCols[i].imgObj.src = datatableBlankImage;
      }

   }

   // Set up an array of reverse sort flags, if not done already.
   var rows = null;
   if (tBody.reverseSort == null)
      tBody.reverseSort = new Array();
   if (tBody.sortedRows == null)
      tBody.sortedRows = new Array();
   else
      rows = tBody.sortedRows[col];

   var tRows = tBody.rows;
   var i;
   if (rows == null)
   {
      rows = new Array();
      for (i = 0; i < tRows.length; i++)
      {
         rows[i] = tRows[i];
      }
   }

   // If this column was the last one sorted, reverse its sort direction.
   if (col == tBody.lastColumn)
   {
     if (tBody.reverseSort[col] == true)
     {
        tBody.reverseSort[col] = false;
        imgObj.src = datatableDownArrowImage;

     }
     else
     {
        sortDirection = 2;
        tBody.reverseSort[col] = true;
        imgObj.src = datatableUpArrowImage;
     }
     rows.reverse();
   }
   // not last one sorted
   else
   {
      if (tBody.lstColImg != null)
         tBody.lstColImg.src=datatableBlankImage;
      imgObj.src=datatableDownArrowImage;

      // if never sorted before then sort

      if (tBody.sortedRows[col] == null)
      {
         tBody.reverseSort[col] = false;
         callSortFunc(cmpFuncObj, rows, col, compareValues, partition);
		 
		 // Modif DD, on inverse le tri pour les nombres
         if (cmpFuncObj.value == datatableCompareNumbers) rows.reverse();
      }

      // else if in past reversed then reverse again else use sorted array as is
      else if (tBody.reverseSort[col] == true)
      {
        tBody.reverseSort[col] = false;
        rows.reverse();
      }

   }
   // insert in the new order
   for (i = 0; i < rows.length; i++)
      tBody.appendChild(rows[i]);

   // Remember this column as the last one sorted.
   tBody.lastColumn = col;
   tBody.lstColImg = imgObj;

   tBody.sortedRows[col] = rows;
   // update sortable columns
   sortedColumns.value = col + datatableOnLoadSubDivider + sortDirection;


   /* tBody.sortedCols is an array of objects where each object contains the properties associated with that column
   like: colId, subArrays,
   sortedCols also is a sorted array with the sequence of cols representing the order in which the cols are sorted
   Save current col in sorted cols array
   */
   tBody.sortedCols = new Array();

   var sortOrder = 1;

   if(tBody.reverseSort != null)
      sortOrder = tBody.reverseSort[col] == true? 2 : 1;


   tBody.sortedCols[0] = new ColObj(col, null, sortOrder, imgObj);

   return false;
}

// This is the function that actually does the sort.
// Notice the event is not passed in.  This function is called both
// from sortTable and initTable which is called when the on load
// event is triggered and we do want to propagate that event
// Cumulative sort will be called if alt key is held down when
// button is pressed and if one column has already been sorted
function doCumulativeSort(tBody, thObj, col)
{
   var tableId = tBody.id;
   var oldDsply = tBody.style.display;
   var imgID = tableId + datatableImageBase + col;
   var imgObj = document.getElementById(imgID);
   var cmpFuncID = tableId + datatableCompareBase + col;
   var cmpFuncObj = document.getElementById(cmpFuncID);

   //get the index of the column in the sortedCols array
   var sortIndex = getIndex(tBody.sortedCols, col);



   var rows = new Array();

   //col has not been sorted
   if(sortIndex == -1)
   {
      //get previous col object from sorted cols
      prevCol = tBody.sortedCols[tBody.sortedCols.length - 1];

      if(prevCol == null)
         return;

      if(getIndex(tBody.sortedCols, prevCol.colId) == 0)
      {
         //assign row id's to all the rows
         for(var i=0; i < tBody.rows.length; i++)
            tBody.rows[i].num = i;
      }

      //create sub arrays for current col and sort them
      var colArray = new Array();

      for(var k=0,i=0; i < tBody.rows.length; k++,i++)
      {
         var tempArray = new Array();
         var j = 0;

         tempArray[0] = tBody.rows[i];
         var start = i;

         while( i != (tBody.rows.length-1) && tBody.rows[i].cells[prevCol.colId].sortValue == tBody.rows[i+1].cells[prevCol.colId].sortValue)
         {
            if(prevCol.subArrays != null)
            {
               //check if the rows belong to the same sub array
               if(!isSameSubArray(tBody.rows[i].num, tBody.rows[i+1].num, prevCol.subArrays))
               {
                  //if not then break
                  break;
               }
            }
            tempArray[j+1] = tBody.rows[i+1];
            i++;
            j++;
         }

         //sort temp array on current col
         callSortFunc(cmpFuncObj, tempArray, col, compareValues, partition);

         for(j=0; j < tempArray.length; j++, start++)
         {
            rows[start] = tempArray[j];
         }

         //store the tempArray as the sub array for the current col
         colArray[k] = tempArray;
      }

      tBody.sortedCols[tBody.sortedCols.length] = new ColObj(col, colArray, 1, imgObj);

      imgObj.src = datatableDownArrowImage;
   }
   else
   {
      var colObj = tBody.sortedCols[sortIndex];

      if(sortIndex == 0)
      {
         //it is the first col to be sorted which needs to be reversed
         //reverse array : do not change/reverse order of rows having same sortValue
         rows = reverseArr(tBody.rows, col);
      }
      else if(sortIndex > 0)
      {
         //it is a column which is sorted based on some previous column
         //hence reverse individual subarrays
         for(var i=0; i < tBody.rows.length; )
         {
            var rowNum = tBody.rows[i].num;

            var arr = new Array();

            //find the subarray which contains the current row id
            for(var j=0; j < colObj.subArrays.length; j++)
            {
               for(var k=0; k < colObj.subArrays[j].length; k++)
               {
                  if(colObj.subArrays[j][k].num == rowNum)
                     arr = colObj.subArrays[j];
               }
            }

            var subArray = new Array();

            if(sortIndex < (tBody.sortedCols.length - 1))
            {
               /*if the column is not the last one sorted in the cummulative sort hierarchy
                 the sub array structure may be modified while sorting the dependent cols.
                 Hence create new sub array based on the previous one.
               */
               subArray[0] = tBody.rows[i];
               i++;

               for(var x = 1; i < tBody.rows.length && getRowIndex(arr, tBody.rows[i].num) != -1; x++, i++)
               {
                  subArray[x] = tBody.rows[i];
               }
            }else
            {
               subArray = arr;
               i = i + subArray.length;
            }

            //reverse array : do not change/reverse order of rows having same sortValue
            subArray = reverseArr(subArray, col);

            colObj.subArrays[j] = subArray;

            if(rows.length == 0)
               rows = subArray;
            else
               rows = rows.concat(subArray);
         }
      }

      //put appropriate sort images
      if(colObj.sortOrder == 1)
      {
         colObj.sortOrder = 2;
         imgObj.src = datatableUpArrowImage;
         colObj.imgObj = imgObj;
      }
      else
      {
         colObj.sortOrder = 1;
         imgObj.src = datatableDownArrowImage;
         colObj.imgObj = imgObj;
      }
   }

   // insert in the new order
   for (i = 0; i < rows.length; i++)
   {
      tBody.appendChild(rows[i]);
   }

   return false;
}

//initializing the properties of the column object
function ColObj(col, colArray, sortOrder, imgObj)
{
   this.colId = col;
   this.subArrays = colArray;
   this.sortOrder = sortOrder;
   this.imgObj = imgObj;
}


/* This function reverses the given array keeping constant the order of rows
   having the same sort values
*/
function reverseArr(rows, col)
{
   var tempArr = new Array();
   var k = 0;

   for(var i = (rows.length-1) ; i >= 0; i--)
   {
      var begin = i;

      while( i != 0 &&  rows[i].cells[col].sortValue == rows[i-1].cells[col].sortValue )
         i--;

      for( var j = i; j <= begin; j++, k++)
         tempArr[k] = rows[j];
   }

   return tempArr;
}

/* This function searches for the index of an element in the given array
*/
function getIndex(arr, value)
{
   for(var i=0; i < arr.length; i++)
   {
      if(arr[i].colId == value)
         return i;
   }

   return -1;
}

/* This function searches for the index of a row in the given array
*/
function getRowIndex(arr, value)
{
   for(var i=0; i < arr.length; i++)
   {
      if( (arr[i].num - value) == 0)
         return i;
   }

   return -1;
}

/* Checks if the given row id's belong to the same sub arrays
*/
function isSameSubArray(rowId1, rowId2, subArrays)
{
   for(var i=0; i < subArrays.length; i++)
   {
      if(getRowIndex(subArrays[i], rowId1) != -1 && getRowIndex(subArrays[i], rowId2) != -1)
      {
         return true;
      }
   }

   return false;
}

/* This function will set the sort values and call quick sort method
*/
function callSortFunc(cmpFuncObj, rows, col, compareValues, partition)
{
   if (cmpFuncObj.value == datatableCompareNumbers)
   {
      setNumbers(rows, col);
      quickSort2(rows, col, compareValues, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareCaseInsensitiveStrings)
   {
      setCaseInsensitiveStrings(rows, col);
      quickSort2(rows, col, compareStrings, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareCaseSensitiveStrings)
   {
      setValues(rows, col);
      quickSort2(rows, col, compareValues, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareDates)
   {
      setDates(rows, col);
      quickSort2(rows, col, compareValues, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareAlphaNumeric)
   {
      var maxlen = setCaseInsensitiveStrings(rows, col);
      rightJustifyAlphhaNumericValues(rows, col, maxlen);
      quickSort2(rows, col, compareStrings, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareRightJustifiedCaseSensitive)
   {
      var maxlen = setValues(rows, col);
      rightJustifyValues(rows, col, maxlen);
      quickSort2(rows, col, compareValues, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareRightJustifiedCaseInsensitive)
   {
      var maxlen = setCaseInsensitiveStrings(rows, col);
      rightJustifyValues(rows, col, maxlen);
      quickSort2(rows, col, compareStrings, partition, 0, rows.length - 1);
   }
   else if (cmpFuncObj.value == datatableCompareDisplayedNames)
   {
      setDisplayedName(rows, col);
      quickSort2(rows, col, compareStrings, partition, 0, rows.length - 1);
   }
   else // if custom function then call it
   {
      setValues(rows, col);
      var cmpFunction = eval(cmpFuncObj.value);
      quickSort2(rows, col, cmpFunction, partition, 0, rows.length - 1);
   }
}

